/*******************************************************************************
 * Copyright 2007, 2009 InnovaSoft SA http://www.innovasoft.biz/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
 
package biz.innovasoft.nodb;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;

import biz.innovasoft.nodb.util.Utils;

/**
 * This is a factory class to open all the repositories. The first time the repository is open,
 * if the repository exists, it gets loaded into memory. This action could take several minutes.
 *  
 * Once the repository is loaded into memory PrevalentRepositoryManager returns a proxy to 
 * access the repository.
 * 
 * @author Diego Miranda
 *
 */
public class RepositoryManager {

	private static RepositoryManager prevalence;
	private Map<String, RepositoryCore> proxies = new HashMap<String, RepositoryCore>();
	
	private Configuration managerConfig = new Configuration();
	private Map<String, FileLock>locks = new HashMap<String,FileLock>();
	
	private RepositoryManager() {
	}

	/**
	 * Returns the instance of the PrevalentRepositoryManager.
	 * 
	 * @return
	 */
	public static RepositoryManager getInstance() {
		if (prevalence == null) {
			prevalence = new RepositoryManager();
		}
		return prevalence;
	}
	
	/**
	 * Allows to change the default configuration.
	 * 
	 * @param conf the new configuration.
	 */
	public void setConfiguration (Configuration conf) {
		this.managerConfig = conf;
	}

	/**
	 * Opens a repository with the specified name. If the repository already exists, it is loaded
	 * into memory.
	 * 
	 * @param name the name of the repository.
	 * @return the repository to work with.
	 * @throws RepositoryException in case of error opening the repository.
	 */
	public Repository openRepository(String name) throws RepositoryException {
		return this.openRepository(name, false);
	}

	/**
	 * Opens a repository with the specified name.
	 * 
	 * @param name the name of the repository.
	 * @param erase if true, the repository is overwritten.
	 * @return the repository to work with.
	 * @throws RepositoryException
	 */
	public Repository openRepository(String name, boolean erase)
			throws RepositoryException {
		RepositoryConfiguration conf = new RepositoryConfiguration ();
		return this.openRepository(name, conf, erase);
	}

	/**
	 * Opens a repository with the specified name.
	 * 
	 * @param name the name of the repository.
	 * @param config the repository configuration.
	 * @param erase if true, the repository is overwritten.
	 * @return the repository to work with.
	 * @throws RepositoryException
	 */
	public Repository openRepository(String name, RepositoryConfiguration config, boolean erase)
		throws RepositoryException {
		
		if (proxies.containsKey(name)) {
			return (Repository) proxies.get(name).getProxy();
		}
		
		if ( config.getRepositoryPath() == null) {
			config.setRepositoryPath(managerConfig.getRepositoriesBasePath() + "/" + name);
		}
		
		boolean lockAquired = acquireLock(name);
		if (erase) {
			if (lockAquired) {
				Utils.delete(config.getRepositoryPath());
			}
		}  

		if (!lockAquired) {
			if (managerConfig.isLockedRepositoryAsTransient()) {
				config.setTransient(true);
			} else {
				throw new RepositoryException ("Repository could not be open. It's being locked by another application.");
			}
		}

		RepositoryProxy proxy = new RepositoryProxy(config);;
		proxies.put(name, proxy.getCore());
		
		return proxy.getCore().getProxy();
	}
	
	/**
	 * Close the repository by the name.
	 * 
	 */
	public void closeRepository (String name) throws RepositoryException {
		RepositoryCore core = proxies.remove(name);
		if (core == null) return;
		core.getPrevalentSystem().close();
		releaseLock(name);
		core = null;
	}
	
	public void closeRepositories () throws RepositoryException {
		Set<String> ks = proxies.keySet();
		for (String key : ks) closeRepository ( key );
	}

	private void releaseLock (String name)  throws RepositoryException {
		FileLock flock = locks.get(name);
		try {
			flock.release();
		} catch (IOException e) {
			throw new RepositoryException ("Error unlocking repository: " + name, e);
		}
	}
	
	private boolean acquireLock(String name) {
		FileLock flock = null;
		try {
			File fileLock = createLockFile(name);
			flock = new RandomAccessFile(fileLock, "rw").getChannel().tryLock();
		} catch (Exception e) {
			throw new RepositoryException("Error locking repository", e);
		}
		if (flock == null) {
			return false;
		}
		locks.put(name, flock);
		return true;
	}

	private File createLockFile(String name) throws Exception {
		File lockPath = new File(managerConfig.getLockPath());
		if (!lockPath.exists()) {
			FileUtils.forceMkdir(lockPath);
		}
		File lockFile = new File(lockPath, name + ".lock");
		if (!lockFile.exists()) {
			lockFile.createNewFile();
		}
		return lockFile;
	}

}
